package es.amh.management;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import com.google.gdata.client.GoogleService.InvalidCredentialsException;
import com.google.gdata.client.Query;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarEventFeed;
import com.google.gdata.data.calendar.CalendarFeed;
import com.google.gdata.data.calendar.ColorProperty;
import com.google.gdata.data.calendar.HiddenProperty;
import com.google.gdata.data.calendar.SelectedProperty;
import com.google.gdata.data.calendar.TimeZoneProperty;
import com.google.gdata.data.extensions.Where;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.NotImplementedException;
import com.google.gdata.util.ServiceException;

import es.amh.data.BirthdayEntry;

/**
 * TODO ofrece las siguientes utilidades: - Fuerza el crear un calendario
 * calendario - Añadir todas las citas - Añadir solo las citas inexistentes -
 * Configurar los recordatorios queq ueremos que tengan los cumpleaños -
 * Devuelve una lista de todos los cumpleaños almacenados
 * 
 * @author ahiges
 * 
 */
public class CalendarManagement implements ICalendarManagement {

	private static final String calendarFeed = "https://www.google.com/calendar/feeds/default/owncalendars/full";

	private final Logger log = Logger.getLogger(this.getClass()
			.getCanonicalName());

	private boolean connected = false;

	private static ICalendarManagement management;

	private CalendarService service;
	
	private int progress;

	/**
	 * Lista de cumpleaños contenida en el calendario. Si el calendario de
	 * internet no contiene ninguna, se hará todo a través del método commit que
	 * además lo guardará aquí
	 */
	private List<BirthdayEntry> birthdays = new ArrayList<BirthdayEntry>();

	private CalendarEntry calendarEntry;

	// TODO coger del fichero de configuración
	/**
	 * Keeps the birthday remember calendar day
	 */
	private final String calendarName = "Birthday's Calendar";

	/**
	 * Devuelve la instancia de BirthdayManagement
	 * 
	 * @return
	 */
	public static ICalendarManagement getInstance() {
		if (management == null) {
			management = new CalendarManagement();
			return management;
		}
		return management;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * es.amh.management.ICalendarManagement#connectService(java.lang.String,
	 * java.lang.String)
	 */
	public boolean connectService(String _user, String _password) {

		try {
			service.setUserCredentials(_user, _password);
			connected = true;

			// Consigue el calendario del programa
			if (calendarEntry == null) {
				calendarEntry = getCalendarEntry();
			}

			return true;
		} catch (InvalidCredentialsException e) {
			return false;
		} catch (AuthenticationException e) {
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see es.amh.management.ICalendarManagement#resetBirthdayCalendar()
	 */
	public CalendarEntry resetBirthdayCalendar() throws NotImplementedException {
		// if (connected) {
		// deleteBirthdayCalendar();
		// commitBirthdays();
		// return createBirthdayCalendar();
		// } else {
		// return null;
		// }
		throw new NotImplementedException("This method is not implemented yet");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see es.amh.management.ICalendarManagement#createBirthdayCalendar()
	 */
	public CalendarEntry createBirthdayCalendar() {
		if (calendarEntry != null) {
			log.info("The calendar already exits");
			return calendarEntry;
		} else {
			calendarEntry = createCalendar();
			log.info("The calendar has been created succesfully");
			return calendarEntry;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see es.amh.management.ICalendarManagement#deleteBirthdayCalendar()
	 */
	public boolean deleteBirthdayCalendar() {
		if (connected) {
			if (calendarEntry == null) {
				log.info("The calendar didn't exist");
				return false;
			}
			if (deleteCalendar(calendarEntry)) {
				calendarEntry = null;
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	/**
	 * Commit hace 3 operaciones: <br>
	 * <li>Commit de todos los cumpleaños en la lista recibidas que no estén ya
	 * en el calendario<br> <li>Si ya están en el calendario comprueba si
	 * requieren de una actualización, en cuyo caso hace commit <br> <li>Los que
	 * están en el calendario que ya no están en la lista recibida se borran
	 * 
	 * Además finalmente muestra estadísticas
	 */
	public int[] commitBirthdays(List<BirthdayEntry> _birthdayList) {
		int commited = 0;
		int updated = 0;
		int deleted = 0;
		int nothing = 0;
		int total = _birthdayList.size();
		int[] output = { 0, 0, 0, 0 };
		List<BirthdayEntry> aux = new ArrayList<BirthdayEntry>();
		// 1) Check if the calendar exits
		if (calendarEntry == null) {
			log.severe("Calendar doesn't exist, create it");
			return output;
		}

		// Init the procces
		long initTime = System.currentTimeMillis();

		URL eventFeedUrl = null;
		try {
			eventFeedUrl = new URL(calendarEntry.getLinks().get(0).getHref());
		} catch (MalformedURLException e1) {
			log.severe(e1.getMessage());
			return output;
		}
		int current = 0;
		progress= 0;
		for (BirthdayEntry b : _birthdayList) {
			progress = Math.round(((float)current/(float)total)*100);
			current++;
			try {

				// Comprueba commits y updates
				if (!this.birthdays.contains(b)) {
					// La entrada no está en el calendario --> Commit
					b = commitBirthday(b, eventFeedUrl);
					if (b == null) {
						log.severe(b + " can't be commited");
					} else {
						aux.add(b);
						commited++;
					}
				} else {
					// La entrada ya estaba en el calendario
					BirthdayEntry b_old = birthdays.get(birthdays.indexOf(b));
					// Comprueba si hay que actualizarlo
					if (b_old.needUpdate(b)) {
						b = update(b_old,b);
						if (b != null) {
							updated++;
							aux.add(b);
						}
					} else {
						aux.add(b_old);
						nothing++;
					}
				}

				// Comprueba necesidades de borrado

			} catch (IOException e) {
				log.severe("Can't commit " + b + " because " + e.getMessage());
			} catch (ServiceException e) {
				log.severe("Can't commit " + b + " because " + e.getMessage());
			}

		}
		for (BirthdayEntry b2 : birthdays) {
			if (!aux.contains(b2)) {
				try {
					if (deleteContact(b2)) {
						deleted++;
					}
				} catch (IOException e) {
					log.severe("Can't delete " + b2 + " because "
							+ e.getMessage());
				} catch (ServiceException e) {
					log.severe("Can't delete " + b2 + " because "
							+ e.getMessage());
				}
			}
		}
		long endTime = System.currentTimeMillis();
		progress = 100;
		log.info("(" + (endTime - initTime)
				+ "millis) Results (number of entries)\t: \n" + commited
				+ "\t: were commited \n" + updated + "\t: were updated\n"
				+ deleted + "\t: were deleted\n" + nothing
				+ "\t: already exists");
		birthdays = aux;
		// Prepara la salida
		output[0] = commited;
		output[1] = updated;
		output[2] = deleted;
		output[3] = nothing;
		return output;
	}

	// //////////////////// PRIVATE INTERFACE

	/**
	 * Devuelve el birthda actualizado
	 */
	private BirthdayEntry update(BirthdayEntry old, BirthdayEntry bnew) throws IOException, ServiceException {
		deleteContact(old);
		URL eventFeedUrl = null;
		try {
			eventFeedUrl = new URL(calendarEntry.getLinks().get(0).getHref());
		} catch (MalformedURLException e1) {
			log.severe(e1.getMessage());
			return null;
		}
		return commitBirthday(bnew, eventFeedUrl);
	}

	private boolean deleteContact(BirthdayEntry b) throws IOException,
			ServiceException {
		b.getEntry().delete();
		return true;
	}

	private BirthdayEntry commitBirthday(BirthdayEntry b, URL eventFeedUrl)
			throws IOException, ServiceException {
		CalendarEventEntry entry = (CalendarEventEntry) service.insert(
				eventFeedUrl, b.getEntry());
		b.setEntry(entry);
		return b;
	}

	/**
	 * Init the service, but doesn't connect
	 */
	private CalendarManagement() {
		service = new CalendarService("M&MSoft-birthdayupdater-1.0");
	}

	private boolean deleteCalendar(CalendarEntry calendarEntry2) {
		try {
			calendarEntry2.delete();
			log.info("Calendar : " + calendarEntry2.getTitle().getPlainText()
					+ " succesfully deleted");
			return true;
		} catch (IOException e) {
			log.severe(e.getMessage());
			return false;
		} catch (ServiceException e) {
			log.severe(e.getMessage());
			return false;
		}
	}

	private CalendarEntry createCalendar() {
		// #711616 Rojo
		// #2952A3 Azul
		if (connected) {
			// TODO sacar la configuración del calendario del fichero
			// Create the calendar
			CalendarEntry calendar = new CalendarEntry();
			calendarEntry = calendar;
			calendar.setSelected(SelectedProperty.TRUE);
			calendar.setTitle(new PlainTextConstruct(this.calendarName));
			calendar.setSummary(new PlainTextConstruct(
					"This calendar contains the reminders of your contact's birthday"));
			calendar.setTimeZone(new TimeZoneProperty("Europe/Madrid"));
			calendar.setHidden(HiddenProperty.FALSE);
			calendar.setColor(new ColorProperty("#2952A3"));
			calendar.addLocation(new Where("", "", "Madrid"));

			// Insert the calendar
			URL postUrl;
			try {
				postUrl = new URL(
						"https://www.google.com/calendar/feeds/default/owncalendars/full");
				return service.insert(postUrl, calendar);
			} catch (MalformedURLException e) {
				log.severe(e.getMessage());
				return null;
			} catch (IOException e) {
				log.severe(e.getMessage());
				return null;
			} catch (ServiceException e) {
				log.severe(e.getMessage());
				return null;
			}

		}
		return null;

	}

	/**
	 * Consigue la referencia al calendario del programa desde google calendar
	 * 
	 * @return Referencia al calendario del programa
	 */
	private CalendarEntry getCalendarEntry() {
		// Send the request and print the response
		try {
			URL feedUrl = new URL(calendarFeed);
			CalendarFeed resultFeed;
			resultFeed = service.getFeed(feedUrl, CalendarFeed.class);
			for (int i = 0; i < resultFeed.getEntries().size(); i++) {
				CalendarEntry entry = resultFeed.getEntries().get(i);
				if (entry.getTitle().getPlainText()
						.equalsIgnoreCase(calendarName)) {
					log.info("Calendary already exits");
					calendarEntry = entry;
					birthdays = recoverBirthdays();
					return entry;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} catch (ServiceException e) {
			log.severe(e.getMessage());
			return null;
		}
		return null;

	}

	/**
	 * Load all the birthdays contented in the calendar
	 * 
	 * @return
	 */
	private List<BirthdayEntry> recoverBirthdays() {
		URL url;
		List<BirthdayEntry> birthList = new ArrayList<BirthdayEntry>();
		try {
			url = new URL(calendarEntry.getLinks().get(0).getHref());
			Query myQuery = new Query(url);
			myQuery.setMaxResults(25);
			myQuery.setFullTextQuery("birthday");
			CalendarEventFeed resultsFeed = service.query(myQuery,
					CalendarEventFeed.class);
			boolean continua = true;
			while (continua) {
				for (CalendarEventEntry entry : resultsFeed.getEntries()) {
					BirthdayEntry be = new BirthdayEntry(entry);
					birthList.add(be);
				}
				if (resultsFeed.getNextLink() != null) {
					URL nextFeed = new URL(resultsFeed.getNextLink().getHref());
					myQuery = new Query(nextFeed);
					resultsFeed = (CalendarEventFeed) service.query(myQuery,
							CalendarEventFeed.class);
				} else {
					continua = false;
				}
			}

		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ServiceException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return birthList;
	}



	/**
	 * @return the progress
	 */
	public int getProgress() {
		return progress;
	}

}
